#!C:\Python31\
#------------------------------------------------------------------------
#               Copyright (c) 1998 by Total Control Software
#                         All Rights Reserved
#------------------------------------------------------------------------
#
# Module Name:  fcgi.py
#
# Description:  Handles communication with the FastCGI module of the
#               web server without using the FastCGI developers kit, but
#               will also work in a non-FastCGI environment, (straight CGI.)
#               This module was originally fetched from someplace on the
#               Net (I don't remember where and I can't find it now...) and
#               has been significantly modified to fix several bugs, be more
#               readable, more robust at handling large CGI data and return
#               document sizes, and also to fit the model that we had previously
#               used for FastCGI.
#
#     WARNING:  If you don't know what you are doing, don't tinker with this
#               module!
#
# Creation Date:    1/30/98 2:59:04PM
#
# License:      This is free software.  You may use this software for any
#               purpose including modification/redistribution, so long as
#               this header remains intact and that you do not claim any
#               rights of ownership or authorship of this software.  This
#               software has been tested, but no warranty is expressed or
#               implied.
#
#
#  burunduk3 rewrittes this 2010-09-25
#  not fully tested yet
#  work in progress
#
#------------------------------------------------------------------------

import cgi, io, socket, struct, sys

#---------------------------------------------------------------------------

# Supported version of the FastCGI protocol
FCGI_VERSION_1 = 1

# This application options
FCGI_MAX_REQS = 1 # Maximum number of requests that can be handled at time
FCGI_MAX_CONNS = 1 # Maximum number of connections that can be handled at time
FCGI_MPXS_CONNS = 0 # Boolean: can this application multiplex connections?


# Set various FastCGI constants

# Record types
FCGI_BEGIN_REQUEST = 1 ; FCGI_ABORT_REQUEST = 2 ; FCGI_END_REQUEST   = 3
FCGI_PARAMS        = 4 ; FCGI_STDIN         = 5 ; FCGI_STDOUT        = 6
FCGI_STDERR        = 7 ; FCGI_DATA          = 8 ; FCGI_GET_VALUES    = 9
FCGI_GET_VALUES_RESULT = 10
FCGI_UNKNOWN_TYPE = 11
FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE

# Types of management records
ManagementTypes = [FCGI_GET_VALUES]

FCGI_NULL_REQUEST_ID=0

# Masks for flags component of FCGI_BEGIN_REQUEST
FCGI_KEEP_CONN = 1

# Values for role component of FCGI_BEGIN_REQUEST
FCGI_RESPONDER = 1 ; FCGI_AUTHORIZER = 2 ; FCGI_FILTER = 3

# Values for protocolStatus component of FCGI_END_REQUEST
FCGI_REQUEST_COMPLETE = 0               # Request completed nicely
FCGI_CANT_MPX_CONN    = 1               # This app can't multiplex
FCGI_OVERLOADED       = 2               # New request rejected; too busy
FCGI_UNKNOWN_ROLE     = 3               # Role value not known



class FCGIException(Exception):
  """FCGIException class? """
  def __init__( self, value ):
    self.value = value
  def __str__( self ):
    return self.value

class FCGIRecord:
  def __init__( self, fcgi_version, record_type, request_id, content = b'' ):
    self.version, self.record_type, self.request_id, self.content = fcgi_version, record_type, request_id, content
  def parse( self ):
    """ parse the content information """
    if self.record_type == FCGI_BEGIN_REQUEST:
      self.role, self.flags = struct.unpack('>HB', self.content[0:3])
    elif self.record_type == FCGI_UNKNOWN_TYPE:
      self.unknown_type = ord(self[0])
    elif self.record_type == FCGI_GET_VALUES or self.record_type == FCGI_PARAMS:
      self.values = {}
      position = 0
      while position < len(self.content):
        name, value, position = self.pair_read(position)
        self.values[name] = value
    elif self.record_type == FCGI_END_REQUEST:
      self.app_status, self.protocol_status = struct.unpack('>IB', self.content[0:5])
  def read( socket ):
    data = socket.recv(8)
    version, record_type, request_id, content_length, padding_length = struct.unpack('>BBHHBx', data)
    content = b''
    while len(content) < content_length:
      content += socket.recv(content_length - len(content))
    while padding_length > 0:
      padding_length -= len(socket.recv(padding_length))
    record = FCGIRecord(version, record_type, request_id, content)
    record.parse()
    return record
  def make_begin( request_id, role, flags ):
    content = struct.pack('>HBxxxxx', role, flags)
    return FCGIRecord(FCGI_VERSION_1, FCGI_BEGIN_REQUEST, request_id, content)
  def make_end( request_id, app_status, protocol_status ):
    content = struct.pack('>IBxxx', app_status, protocol_status)
    return FCGIRecord(FCGI_VERSION_1, FCGI_END_REQUEST, request_id, content)
  def make_get_values( request_id, values ):
    content = b''.join([pair_write(key, values[key]) for key in values.keys()])
    return FCGIRecord(FCGI_VERSION_1, FCGI_GET_VALUES, request_id, content)
  def make_unknown( request_id, unknown_type ):
    content = struct.pack('Bxxxxxxx', unknown_type)
    return FCGIRecord(FCGI_VERSION_1, FCGI_UNKNOWN_TYPE, request_id, content)
  def write( self, socket ):
    length = len(self.content)
    padding = (8 - (length & 7)) & 7
    header = struct.pack('>BBHHBB', self.version, self.record_type, self.request_id, length, padding, 0)
    socket.send(header + self.content + b'\0' * padding)
  def pair_read( self, position ):
    name_length = self.content[position]
    if (name_length & 0x80) != 0:
      name_length = struct.unpack('>I', self.content[position:position + 4])[0] & 0x7ffffff<F11>f
      position += 4
    else:
      position += 1
    value_length = self.content[position]
    if (value_length & 0x80) != 0:
      value_length = struct.unpack('>I', self.content[position:position + 4])[0] & 0x7fffffff
      position += 4
    else:
      position += 1
    return self.content[position:position + name_length], self.content[position + name_length:position + name_length + value_length], position + name_length + value_length
  def pair_write( name, value ):
    length = [l if l < 128 else l + 0x10000000 for l in (len(name), len(value))]
    s = b''.join([struct.pack('B' if l < 128 else '>I') for l in length])
    return s + name + value

class FCGIRequest:
  def __init__( self, socket, request_id, env, input ):
    self.socket, self.request_id, self.env, self.input = socket, request_id, env, input
    self.stdin, self.stdout, self.stderr = io.StringIO(input), io.StringIO(), io.StringIO()
  def cgi_field_storage( self ):
    method = self.env['REQUEST_METHOD'] if 'REQUEST_METHOD' in self.env else 'GET'
    return cgi.FieldStorage(environ = self.env, keep_blank_values = 1) if method == 'GET' else\
           cgi.FieldStorage(fp = self.stdin, environ = self.env, keep_blank_values = 1)
    # TODO: wtf? always return with fp!
  def write( self, value ):
    # TODO: handle if value is bigger than 64K (several writes)
    # TODO: write only at end, but support flush()
    record = FCGIRecord(FCGI_VERSION_1, FCGI_STDOUT, self.request_id, value.encode())
    record.write(self.socket)
  def finish( self, status = 0 ):
    record = FCGIRecord.make_end(self.request_id, status, FCGI_REQUEST_COMPLETE)
    record.write(self.socket)
    self.socket.close()
    self.socket, self.request_id, self.env, self.input = None, None, None, None

class FCGI:
  def __init__( self, debug = lambda s: None ):
    self.debug = debug
    self.socket = socket.fromfd(sys.stdin.fileno(), socket.AF_UNIX, socket.SOCK_STREAM)
    # TODO: check fcgi. Documentation suggests something strange.
    self.variables = {'FCGI_MAX_CONNS' : FCGI_MAX_CONNS, 'FCGI_MAX_REQS'  : FCGI_MAX_REQS, 'FCGI_MPXS_CONNS': FCGI_MPXS_CONNS}
  def accept( self ):
    connection, address = self.socket.accept()
    # TODO: check address using environ FCGI_WEB_SERVER_ADDRS
    record = FCGIRecord.read(connection)
    if record.record_type != FCGI_BEGIN_REQUEST:
      raise FCGIException('FCGI_BEGIN_REQUEST expected')
    request_id = record.request_id
    remaining = {FCGI_AUTHORIZER: 1, FCGI_RESPONDER: 2, FCGI_FILTER: 3}[record.role]
    env, input = {}, b''
    while remaining != 0:
      record = FCGIRecord.read(connection)
      if record.record_type == FCGI_PARAMS:
        if record.content == b'':
          remaining -= 1
        else:
          for key in record.values.keys():
            value = record.values[key].decode()
            env[key.decode()] = value
      elif record.record_type == FCGI_STDIN:
        if record.content == b'':
          remaining -= 1
        else:
          input += record.content
    return FCGIRequest(connection, request_id, env, input.decode(), )

##---------------------------------------------------------------------------

def HandleManTypes(r, conn):
    if r.recType == FCGI_GET_VALUES:
        r.recType = FCGI_GET_VALUES_RESULT
        v={}
        #vars={'FCGI_MAX_CONNS' : FCGI_MAX_CONNS,
        #      'FCGI_MAX_REQS'  : FCGI_MAX_REQS,
        #      'FCGI_MPXS_CONNS': FCGI_MPXS_CONNS}
        for i in r.values.keys():
            if vars.has_key(i): v[i]=vars[i]
        r.values=vars
        r.writeRecord(conn)

#---------------------------------------------------------------------------
#---------------------------------------------------------------------------


_isFCGI = 1         # assume it is until we find out for sure

def isFCGI():
    global _isFCGI
    return _isFCGI



#---------------------------------------------------------------------------


_init = None
_sock = None

class __old_FCGI:
    def __init__(self):
        self.inp, self.out, self.err, self.env = None, None, None, None
        self.haveFinished = 0
        if _init == None:
            _startup()
        if not isFCGI():
            self.haveFinished = 1
            self.inp, self.out, self.err, self.env = \
                                sys.stdin, sys.stdout, sys.stderr, os.environ
            return

        if 'FCGI_WEB_SERVER_ADDRS' in os.environ:
            good_addrs=string.split(os.environ['FCGI_WEB_SERVER_ADDRS'], ',')
            good_addrs=map(string.strip(good_addrs))        # Remove whitespace
        else:
            good_addrs=None

        self.conn, addr=_sock.accept()
        stdin, data="", ""
        self.env = {}
        self.requestId=0
        remaining=1

        # Check if the connection is from a legal address
        #if good_addrs!=None and addr not in good_addrs:
        #    raise (error, 'Connection from invalid server!')

        while remaining:
            r=record(); r.readRecord(self.conn)

            if r.recType in ManagementTypes:
                HandleManTypes(r, self.conn)

            elif r.reqId==0:
                # Oh, poopy.  It's a management record of an unknown
                # type.  Signal the error.
                r2=record()
                r2.recType=FCGI_UNKNOWN_TYPE ; r2.unknownType=r.recType
                r2.writeRecord(self.conn)
                continue                # Charge onwards

            # Ignore requests that aren't active
            elif r.reqId != self.requestId and r.recType != FCGI_BEGIN_REQUEST:
                continue

            # If we're already doing a request, ignore further BEGIN_REQUESTs
            elif r.recType == FCGI_BEGIN_REQUEST and self.requestId != 0:
                continue

            # Begin a new request
            #if r.recType == FCGI_BEGIN_REQUEST:
            #    self.requestId = r.reqId
            #    if r.role == FCGI_AUTHORIZER:   remaining=1
            #    elif r.role == FCGI_RESPONDER:  remaining=2
            #    elif r.role == FCGI_FILTER:     remaining=3

            #elif r.recType == FCGI_PARAMS:
            #if r.recType == FCGI_PARAMS:
            #    if r.content == "":
            #        remaining=remaining-1
            #    else:
            #        for i in r.values.keys():
            #            self.env[i] = r.values[i]

            #elif r.recType == FCGI_STDIN:
            #if r.recType == FCGI_STDIN:
            #    if r.content == "":
            #        remaining=remaining-1
            #    else:
            #        stdin=stdin+r.content

            #elif r.recType==FCGI_DATA:
            if r.recType==FCGI_DATA:
                if r.content == "":
                    remaining=remaining-1
                else:
                    data=data+r.content
        # end of while remaining:

        #self.inp = sys.stdin  = StringIO(stdin)
        #self.err = sys.stderr = StringIO()
        #self.out = sys.stdout = StringIO()
        self.data = StringIO(data)

    def __del__(self):
        self.Finish()

    def Finish(self, status=0):
        if not self.haveFinished:
            self.haveFinished = 1

            if self.err is not None:
              self.err.seek(0,0)
            if self.out is not None:
              self.out.seek(0,0)

            r=record()
            r.recType = FCGI_STDERR
            r.reqId = self.requestId
            data = self.err.read()
            if data:
                while data:
                    chunk, data = self.getNextChunk(data)
                    r.content = chunk
                    r.writeRecord(self.conn)
                r.content="" ; r.writeRecord(self.conn)      # Terminate stream

            r.recType = FCGI_STDOUT
            data = self.out.read()
            while data:
                chunk, data = self.getNextChunk(data)
                r.content = chunk
                r.writeRecord(self.conn)
            r.content="" ; r.writeRecord(self.conn)      # Terminate stream

            #r=record()
            #r.recType=FCGI_END_REQUEST
            #r.reqId=self.requestId
            #r.appStatus=status
            #r.protocolStatus=FCGI_REQUEST_COMPLETE
            #r.writeRecord(self.conn)
            #self.conn.close()


    #def getFieldStorage(self):
    #    method = 'GET'
    #    if 'REQUEST_METHOD' in self.env:
    #        method = string.upper(self.env['REQUEST_METHOD'])
    #    if method == 'GET':
    #        return cgi.FieldStorage(environ=self.env, keep_blank_values=1)
    #    else:
    #        return cgi.FieldStorage(fp=self.inp, environ=self.env, keep_blank_values=1)

    def getNextChunk(self, data):
        chunk = data[:8192]
        data = data[8192:]
        return chunk, data


#Accept = FCGI       # alias for backwards compatibility
#---------------------------------------------------------------------------

#def _startup():
#    global _init
#    _init = 1
#    try:
#        s=socket.fromfd(sys.stdin.fileno(), socket.AF_INET,
#                        socket.SOCK_STREAM)
#        #pn = s.getpeername() // too compicated check
#    except socket.error as error:
#        if error != errno.ENOTCONN:       # must be a non-fastCGI environment
#            global _isFCGI
#            _isFCGI = 0
#            return
#
#    global _sock
#    _sock = s


#---------------------------------------------------------------------------

def _test():
    counter=0
    print ("ok");
    try:
        while isFCGI():
            req = Accept()
            counter=counter+1

            try:
                fs = req.getFieldStorage()
                size = string.atoi(fs['size'].value)
                doc = ['*' * size]
            except:
                doc = ['<HTML><HEAD><TITLE>FCGI TestApp</TITLE></HEAD>\n<BODY>\n']
                doc.append('<H2>FCGI TestApp</H2><P>')
                doc.append('<b>request count</b> = %d<br>' % counter)
                doc.append('</BODY></HTML>\n')


            doc = string.join(doc, '')
            req.out.write('Content-length: %s\r\n'
                        'Content-type: text/html\r\n'
                        'Cache-Control: no-cache\r\n'
                        '\r\n'
                            % len(doc))
            req.out.write(doc)

            req.Finish()
    except:
        import traceback
        f = open('traceback', 'w')
        traceback.print_exc( file = f )
#        f.write('%s' % doc)

if __name__=='__main__':
    #import pdb
    #pdb.run('_test()')
    _test()
